package com.gitee.jdbc.elasticsearch;

import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Map;
import java.util.Properties;
import org.slf4j.LoggerFactory;

public class JdbcDriver implements Driver {

  private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JdbcDriver.class.getName());
  public static final String PREFIX = "jdbc:jest:";
  public static final int PORT = 9200;

  /**
   * Register this driver to the driver manager
   */
  static {
    try {
      DriverManager.registerDriver(new JdbcDriver());
    } catch (SQLException se) {
      logger.error("Unable to register Driver", se);
    }
  }

  /**
   * Connect to the database using a URL like : jdbc:jest://host:port[?options]]
   */
  @Override
  public Connection connect(String url, Properties info) throws SQLException {
    if (!acceptsURL(url)) {
      return null;
    }
    Object[] conInfo = parseURL(url, info);
    String host = (String) conInfo[0];
    int port = (int) conInfo[1];
    Properties props = (Properties) conInfo[2];
    return new ElasticsearchConnection(host, port, props);
  }

  /**
   * Parses the url and returns information required to create a connection. Properties in the url are added to the
   * provided properties and returned in the object array
   *
   * @param url
   * @param info
   * @return {String:host, int:port, Properties:info}
   * @throws SQLException
   */
  private Object[] parseURL(String url, Properties info) throws SQLException {
    if (!acceptsURL(url)) {
      throw new SQLException("Invalid url");
    }
    try {
      URI uri = new URI(url.substring(PREFIX.length()));
      String host = uri.getHost();
      int port = (uri.getPort() < 0 ? PORT : uri.getPort());
      Properties props = Constants.defaultProps();
      if (info != null) {
        props.putAll(info);
      }
      info = props;
      if (uri.getQuery() != null) {
        for (String keyValue : uri.getQuery().split("&")) {
          String[] parts = keyValue.split("=");
          if (parts.length > 1) {
            info.setProperty(parts[0].trim(), parts[1].trim());
          } else {
            info.setProperty(parts[0], "");
          }
        }
      }
      return new Object[]{host, port, info};
    } catch (URISyntaxException e) {
      throw new SQLException(
          "Unable to parse URL. Pleas use '" + PREFIX + "//host:port?(param=value&)*'", e);
    } catch (ArrayIndexOutOfBoundsException e) {
      throw new SQLException(
          "No schema (index) specified. Pleas use '" + PREFIX + "//host:port?(param=value&)*'");
    } catch (Exception e) {
      throw new SQLException("Unable to connect to database due to: " + e.getClass().getName(), e);
    }
  }

  @Override
  public boolean acceptsURL(String url) throws SQLException {
    if (!url.startsWith(PREFIX)) {
      return false;
    }
    try {
      URI uri = new URI(url.substring(5));
      if (uri.getHost() == null) {
        throw new SQLException("Invalid URL, no host specified");
      }
      if (uri.getPath() == null) {
        throw new SQLException("Invalid URL, no index specified");
      }
      if (uri.getPath().split("/").length > 2) {
        throw new SQLException("Invalid URL, " + uri.getPath() + " is not a valid index");
      }
    } catch (URISyntaxException e) {
      throw new SQLException("Unable to parse URL", e);
    }
    return true;
  }

  @Override
  public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
    Properties props = (Properties) parseURL(url, info)[3];
    DriverPropertyInfo[] result = new DriverPropertyInfo[props.size()];
    int index = 0;
    for (Map.Entry<Object, Object> entry : props.entrySet()) {
      result[index] = new DriverPropertyInfo((String) entry.getKey(), entry.getValue().toString());
      index++;
    }
    return result;
  }

  @Override
  public int getMajorVersion() {
    return Constants.ES_MAJOR_VERSION;
  }

  @Override
  public int getMinorVersion() {
    return Constants.ES_MINOR_VERSION;
  }

  @Override
  public boolean jdbcCompliant() {
    return false;
  }

  @Override
  public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

}
